package cn.dansj.common.utils.transfer;

import cn.dansj.common.utils.json.JSONUtils;
import com.fasterxml.jackson.core.type.TypeReference;

import java.lang.reflect.Array;
import java.util.*;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public abstract class ArrayUtils {
    public static final Boolean[] EMPTY_BOOLEAN_OBJECT_ARRAY = {};

    public static final Byte[] EMPTY_BYTE_OBJECT_ARRAY = {};

    public static final Character[] EMPTY_CHARACTER_OBJECT_ARRAY = {};

    public static final Double[] EMPTY_DOUBLE_OBJECT_ARRAY = {};

    public static final Float[] EMPTY_FLOAT_OBJECT_ARRAY = {};

    public static final Integer[] EMPTY_INTEGER_OBJECT_ARRAY = {};

    public static final Long[] EMPTY_LONG_OBJECT_ARRAY = {};

    public static final Short[] EMPTY_SHORT_OBJECT_ARRAY = {};

    public static final String[] EMPTY_STRING_ARRAY = {};

    @SafeVarargs
    public static <E> List<E> asList(List<? extends E>... list) {
        if (list == null) return new ArrayList<>();
        List<E> result = new ArrayList<>(Long.valueOf(Arrays.stream(list).filter(Objects::nonNull).mapToLong(List::size).sum()).intValue());
        for (List<? extends E> l : list) {
            if (l == null) continue;
            result.addAll(l);
        }
        return result;
    }

    @SafeVarargs
    public static <T> List<T> asList(T... t) {
        if (t == null) return new ArrayList<>();
        return Arrays.stream(t).collect(Collectors.toList());
    }

    public static <T> List<T> asList(Iterator<T> iterator) {
        if (iterator == null) return new ArrayList<>();
        List<T> result = new ArrayList<>();
        iterator.forEachRemaining(result::add);
        return result;
    }

    @SafeVarargs
    public static <T> Set<T> asSet(T... list) {
        return new HashSet<>(asList(list));
    }

    @SafeVarargs
    public static <T> Set<T> asSet(Set<T>... set) {
        final HashSet<T> ts = new HashSet<>();
        if (set == null) {
            return ts;
        }
        for (Set<T> s : set) {
            ts.addAll(s);
        }
        return ts;
    }

    public static <T> List<T> subList(Collection<T> collection, int offset, int limit) {
        List<T> list = new ArrayList<>(collection);
        if (limit < 1) {
            limit = 1;
        }
        int maxSize = list.size();
        int mod = Math.floorMod(maxSize, limit);
        int maxOffset = maxSize / limit;
        maxOffset = Math.round((float) maxOffset);
        maxOffset = mod == 0 ? maxOffset : maxOffset + 1;
        int maxSub = offset * limit;

        if (maxSub > maxSize) {
            maxSub = maxSize;
        }

        if (offset > maxOffset) {
            offset = maxOffset;
        }

        if (offset < 1) {
            offset = 1;
        }
        return list.subList((offset - 1) * limit, maxSub);
    }

    public static <T> List<T> asList(Set<T> set) {
        return new ArrayList<>(set);
    }

    @SafeVarargs
    public static <T> List<T> asList(T[]... arrays) {
        if (arrays == null) return new ArrayList<>();
        return Arrays.stream(arrays).map(ArrayUtils::concat).flatMap(Arrays::stream).collect(Collectors.toList());
    }

    public static List<Integer> asList(int[]... arrays) {
        return Arrays.stream(arrays).map(ArrayUtils::toObject).flatMap(Arrays::stream).collect(Collectors.toList());
    }

    public static List<Short> asList(short[]... arrays) {
        return Arrays.stream(arrays).map(ArrayUtils::toObject).flatMap(Arrays::stream).collect(Collectors.toList());
    }

    public static List<Double> asList(double[]... arrays) {
        return Arrays.stream(arrays).map(ArrayUtils::toObject).flatMap(Arrays::stream).collect(Collectors.toList());
    }

    public static List<Float> asList(float[]... arrays) {
        return Arrays.stream(arrays).map(ArrayUtils::toObject).flatMap(Arrays::stream).collect(Collectors.toList());
    }

    public static List<Long> asList(long[]... arrays) {
        return Arrays.stream(arrays).map(ArrayUtils::toObject).flatMap(Arrays::stream).collect(Collectors.toList());
    }

    public static List<Character> asList(char[]... arrays) {
        return Arrays.stream(arrays).map(ArrayUtils::toObject).flatMap(Arrays::stream).collect(Collectors.toList());
    }

    public static List<Byte> asList(byte[]... arrays) {
        return Arrays.stream(arrays).map(ArrayUtils::toObject).flatMap(Arrays::stream).collect(Collectors.toList());
    }

    public static List<Boolean> asList(boolean[]... arrays) {
        return Arrays.stream(arrays).map(ArrayUtils::toObject).flatMap(Arrays::stream).collect(Collectors.toList());
    }

    @SafeVarargs
    public static <T> T[] reverse(T... v) {
        final List<T> tList = asList(v);
        Collections.reverse(tList);
        return tList.toArray(v);
    }

    public static boolean[] concat(final boolean[] array1, final boolean... array2) {
        if (array1 == null) {
            return array2 != null ? array2.clone() : null;
        }
        if (array2 == null) {
            return array1.clone();
        }
        final boolean[] joinedArray = new boolean[array1.length + array2.length];
        System.arraycopy(array1, 0, joinedArray, 0, array1.length);
        System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
        return joinedArray;
    }

    public static byte[] concat(final byte[] array1, final byte... array2) {
        if (array1 == null) {
            return array2 != null ? array2.clone() : null;
        }
        if (array2 == null) {
            return array1.clone();
        }
        final byte[] joinedArray = new byte[array1.length + array2.length];
        System.arraycopy(array1, 0, joinedArray, 0, array1.length);
        System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
        return joinedArray;
    }

    public static char[] concat(final char[] array1, final char... array2) {
        if (array1 == null) {
            return array2 != null ? array2.clone() : null;
        }
        if (array2 == null) {
            return array1.clone();
        }
        final char[] joinedArray = new char[array1.length + array2.length];
        System.arraycopy(array1, 0, joinedArray, 0, array1.length);
        System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
        return joinedArray;
    }

    public static double[] concat(final double[] array1, final double... array2) {
        if (array1 == null) {
            return array2 != null ? array2.clone() : null;
        }
        if (array2 == null) {
            return array1.clone();
        }
        final double[] joinedArray = new double[array1.length + array2.length];
        System.arraycopy(array1, 0, joinedArray, 0, array1.length);
        System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
        return joinedArray;
    }

    public static float[] concat(final float[] array1, final float... array2) {
        if (array1 == null) {
            return array2 != null ? array2.clone() : null;
        }
        if (array2 == null) {
            return array1.clone();
        }
        final float[] joinedArray = new float[array1.length + array2.length];
        System.arraycopy(array1, 0, joinedArray, 0, array1.length);
        System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
        return joinedArray;
    }

    public static int[] concat(final int[] array1, final int... array2) {
        if (array1 == null) {
            return array2 != null ? array2.clone() : null;
        }
        if (array2 == null) {
            return array1.clone();
        }
        final int[] joinedArray = new int[array1.length + array2.length];
        System.arraycopy(array1, 0, joinedArray, 0, array1.length);
        System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
        return joinedArray;
    }

    public static long[] concat(final long[] array1, final long... array2) {
        if (array1 == null) {
            return array2 != null ? array2.clone() : null;
        }
        if (array2 == null) {
            return array1.clone();
        }
        final long[] joinedArray = new long[array1.length + array2.length];
        System.arraycopy(array1, 0, joinedArray, 0, array1.length);
        System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
        return joinedArray;
    }

    public static short[] concat(final short[] array1, final short... array2) {
        if (array1 == null) {
            return array2 != null ? array2.clone() : null;
        }
        if (array2 == null) {
            return array1.clone();
        }
        final short[] joinedArray = new short[array1.length + array2.length];
        System.arraycopy(array1, 0, joinedArray, 0, array1.length);
        System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
        return joinedArray;
    }

    @SafeVarargs
    public static <T> T[] concat(final T[] array1, final T... array2) {
        if (array1 == null) {
            return array2 != null ? array2.clone() : null;
        }
        if (array2 == null) {
            return array1.clone();
        }
        final Class<T> type1 = getComponentType(array1);
        final T[] joinedArray = arraycopy(array1, 0, 0, array1.length, () -> newInstance(type1, array1.length + array2.length));
        try {
            System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
        } catch (final ArrayStoreException ase) {
            final Class<?> type2 = array2.getClass().getComponentType();
            if (!type1.isAssignableFrom(type2)) {
                throw new IllegalArgumentException("Cannot store " + type2.getName() + " in an array of " + type1.getName(), ase);
            }
            throw ase;
        }
        return joinedArray;
    }

    public static Boolean[] toObject(final boolean[] array) {
        if (array == null) {
            return null;
        }
        if (array.length == 0) {
            return EMPTY_BOOLEAN_OBJECT_ARRAY;
        }
        final Boolean[] result = new Boolean[array.length];
        return setAll(result, i -> array[i] ? Boolean.TRUE : Boolean.FALSE);
    }

    public static Byte[] toObject(final byte[] array) {
        if (array == null) {
            return null;
        }
        if (array.length == 0) {
            return EMPTY_BYTE_OBJECT_ARRAY;
        }
        return setAll(new Byte[array.length], i -> array[i]);
    }

    public static Character[] toObject(final char[] array) {
        if (array == null) {
            return null;
        }
        if (array.length == 0) {
            return EMPTY_CHARACTER_OBJECT_ARRAY;
        }
        return setAll(new Character[array.length], i -> array[i]);
    }

    public static Double[] toObject(final double[] array) {
        if (array == null) {
            return null;
        }
        if (array.length == 0) {
            return EMPTY_DOUBLE_OBJECT_ARRAY;
        }
        return setAll(new Double[array.length], i -> array[i]);
    }

    public static Float[] toObject(final float[] array) {
        if (array == null) {
            return null;
        }
        if (array.length == 0) {
            return EMPTY_FLOAT_OBJECT_ARRAY;
        }
        return setAll(new Float[array.length], i -> array[i]);
    }

    public static Integer[] toObject(final int[] array) {
        if (array == null) {
            return null;
        }
        if (array.length == 0) {
            return EMPTY_INTEGER_OBJECT_ARRAY;
        }
        return setAll(new Integer[array.length], i -> array[i]);
    }

    public static Long[] toObject(final long[] array) {
        if (array == null) {
            return null;
        }
        if (array.length == 0) {
            return EMPTY_LONG_OBJECT_ARRAY;
        }
        return setAll(new Long[array.length], i -> array[i]);
    }

    public static Short[] toObject(final short[] array) {
        if (array == null) {
            return null;
        }
        if (array.length == 0) {
            return EMPTY_SHORT_OBJECT_ARRAY;
        }
        return setAll(new Short[array.length], i -> array[i]);
    }

    private static <T> Class<T> getComponentType(final T[] array) {
        return getComponentType(getClass(array));
    }

    @SuppressWarnings("unchecked")
    private static <T> Class<T> getClass(final T object) {
        return object == null ? null : (Class<T>) object.getClass();
    }

    @SuppressWarnings("unchecked")
    private static <T> Class<T> getComponentType(final Class<T[]> cls) {
        return cls == null ? null : (Class<T>) cls.getComponentType();
    }

    public static <T> T arraycopy(final T source, final int sourcePos, final int destPos, final int length, final Function<Integer, T> allocator) {
        return arraycopy(source, sourcePos, allocator.apply(length), destPos, length);
    }

    public static <T> T arraycopy(final T source, final int sourcePos, final int destPos, final int length, final Supplier<T> allocator) {
        return arraycopy(source, sourcePos, allocator.get(), destPos, length);
    }

    public static <T> T arraycopy(final T source, final int sourcePos, final T dest, final int destPos, final int length) {
        System.arraycopy(source, sourcePos, dest, destPos, length);
        return dest;
    }

    @SuppressWarnings("unchecked")
    public static <T> T[] newInstance(final Class<T> componentType, final int length) {
        return (T[]) Array.newInstance(componentType, length);
    }

    public static <T> T[] setAll(final T[] array, final IntFunction<? extends T> generator) {
        if (array != null && generator != null) {
            Arrays.setAll(array, generator);
        }
        return array;
    }

    public static <T> T[] setAll(final T[] array, final Supplier<? extends T> generator) {
        if (array != null && generator != null) {
            for (int i = 0; i < array.length; i++) {
                array[i] = generator.get();
            }
        }
        return array;
    }

    public static <T, C extends Collection<T>> C removeIf(final C collection, final Predicate<? super T> predicate) {
        if (Verification.checkNull(collection)) return collection;
        C obj = JSONUtils.toObj(JSONUtils.toJSON(collection), new TypeReference<C>() {
        });
        obj.removeIf(predicate);
        return obj;
    }
}
